Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Node / ReticulumProjects / MeshChatX.git / files / tests / backend / test_integrity_extensive.py

Displaying Raw • Download


tests/backend/test_integrity_extensive.py v4.1.0 (aa8575e3) Text, 7.06 KB

Tff7b72import T7ee787shutil
Tff7b72import T7ee787tempfile
Tff7b72import T7ee787unittest
Tff7b72import T7ee787os
Tff7b72import T7ee787sqlite3
Tff7b72import T7ee787json
Tff7b72from T7ee787pathlib Tff7b72import Te6edf3Path
Tff7b72from T7ee787hypothesis Tff7b72import Te6edf3givenTb4b4b4, Te6edf3strategies Tff7b72as Te6edf3stTb4b4b4, Te6edf3settingsTb4b4b4, Te6edf3HealthCheck

Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787integrity_manager Tff7b72import Te6edf3IntegrityManager


Tff7b72class T56d364TestIntegrityManagerExtensiveTb4b4b4(Te6edf3unittestTff7b72.Td2a8ffTestCaseTb4b4b4)Tb4b4b4:
Tff7b72def Td2a8ffsetUpTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72= Te6edf3PathTb4b4b4(Te6edf3tempfileTff7b72.Td2a8ffmkdtempTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdb_path Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffdatabase.dbTa5d6ff"
Tff7b72selfTff7b72.Td2a8ffstorage_dir Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffstorageTa5d6ff"
Tff7b72selfTff7b72.Td2a8ffstorage_dirTff7b72.Td2a8ffmkdirTb4b4b4(Tb4b4b4)

T8b949e# Create a valid SQLite database
Te6edf3conn Tff7b72= Te6edf3sqlite3Tff7b72.Td2a8ffconnectTb4b4b4(Tff7b72selfTff7b72.Td2a8ffdb_pathTb4b4b4)
Te6edf3connTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffCREATE TABLE data (id INTEGER PRIMARY KEY, val TEXT)Ta5d6ff"Tb4b4b4)
Te6edf3connTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffINSERT INTO data (val) VALUES (Ta5d6ff'Ta5d6ffinitialTa5d6ff'Ta5d6ff)Ta5d6ff"Tb4b4b4)
Te6edf3connTff7b72.Td2a8ffcommitTb4b4b4(Tb4b4b4)
Te6edf3connTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ffmanager Tff7b72= Te6edf3IntegrityManagerTb4b4b4(Tff7b72selfTff7b72.Td2a8fftest_dirTb4b4b4, Tff7b72selfTff7b72.Td2a8ffdb_pathTb4b4b4)

Tff7b72def Td2a8fftearDownTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
Te6edf3shutilTff7b72.Td2a8ffrmtreeTb4b4b4(Tff7b72selfTff7b72.Td2a8fftest_dirTb4b4b4)

Tff7b72def Td2a8fftest_entropy_mathematical_boundsTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Verify entropy stays within [0, 8] for any byte sequence."""
T8b949e# Test empty
Te6edf3empty_file Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffemptyTa5d6ff"
Te6edf3empty_fileTff7b72.Td2a8fftouchTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertEqualTb4b4b4(Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ff_calculate_entropyTb4b4b4(Te6edf3empty_fileTb4b4b4)Tb4b4b4, T79c0ff0Tb4b4b4)

T8b949e# Test single byte repeated (minimum entropy)
Te6edf3zero_entropy_file Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffzero_entropyTa5d6ff"
Tff7b72with Tffa657openTb4b4b4(Te6edf3zero_entropy_fileTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Ta5d6ffbTa5d6ff"Ta5d6ffAAAAAAAATa5d6ff" Tff7b72* T79c0ff100Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertEqualTb4b4b4(Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ff_calculate_entropyTb4b4b4(Te6edf3zero_entropy_fileTb4b4b4)Tb4b4b4, T79c0ff0Tb4b4b4)

T8b949e# Test all 256 bytes (maximum entropy)
Te6edf3max_entropy_file Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffmax_entropyTa5d6ff"
Tff7b72with Tffa657openTb4b4b4(Te6edf3max_entropy_fileTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Tffa657bytesTb4b4b4(Tffa657rangeTb4b4b4(T79c0ff256Tb4b4b4)Tb4b4b4)Tb4b4b4)
T8b949e# log2(256) = 8
Tff7b72selfTff7b72.Td2a8ffassertAlmostEqualTb4b4b4(
Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ff_calculate_entropyTb4b4b4(Te6edf3max_entropy_fileTb4b4b4)Tb4b4b4, T79c0ff8.0Tb4b4b4, Te6edf3placesTff7b72=T79c0ff5
Tb4b4b4)

Tf0883e@settingsTb4b4b4(Te6edf3suppress_health_checkTff7b72=Tb4b4b4[Te6edf3HealthCheckTff7b72.Td2a8fftoo_slowTb4b4b4]Tb4b4b4, Te6edf3deadlineTff7b72=Tff7b72NoneTb4b4b4)
Tf0883e@givenTb4b4b4(Te6edf3stTff7b72.Td2a8ffbinaryTb4b4b4(Te6edf3min_sizeTff7b72=T79c0ff1Tb4b4b4, Te6edf3max_sizeTff7b72=T79c0ff1024Tb4b4b4)Tb4b4b4)
Tff7b72def Td2a8fftest_entropy_propertyTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3dataTb4b4b4)Tb4b4b4:
T8b949e"""Property: Entropy is always between 0 and 8 for non-empty data."""
Te6edf3temp_file Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffprop_testTa5d6ff"
Tff7b72with Tffa657openTb4b4b4(Te6edf3temp_fileTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3dataTb4b4b4)

Te6edf3entropy Tff7b72= Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ff_calculate_entropyTb4b4b4(Te6edf3temp_fileTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertGreaterEqualTb4b4b4(Te6edf3entropyTb4b4b4, T79c0ff0Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertLessEqualTb4b4b4(Te6edf3entropyTb4b4b4, T79c0ff8.000000000000002Tb4b4b4) T8b949e# Float precision

Tff7b72def Td2a8fftest_db_structural_tamper_detectionTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Simulate actual SQLite corruption that bypasses hash-only checks."""
Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffsave_manifestTb4b4b4(Tb4b4b4)

T8b949e# Corrupt the database file header or internal structure
T8b949e# Overwriting the first few bytes (SQLite header) is a guaranteed fail
Tff7b72with Tffa657openTb4b4b4(Tff7b72selfTff7b72.Td2a8ffdb_pathTb4b4b4, Ta5d6ff"Ta5d6ffr+bTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffseekTb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Ta5d6ffbTa5d6ff"Ta5d6ffNOTASQLITEFILETa5d6ff"Tb4b4b4)

Te6edf3is_okTb4b4b4, Te6edf3issues Tff7b72= Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffcheck_integrityTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertFalseTb4b4b4(Te6edf3is_okTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffIntegrity should fail. Issues: Tffd700{Te6edf3issuesTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertTrueTb4b4b4(
Tffa657anyTb4b4b4(
Ta5d6ff"Ta5d6ffDatabase structural issueTa5d6ff" Tff7b72in Te6edf3i Tff7b72or Ta5d6ff"Ta5d6ffDatabase structural anomalyTa5d6ff" Tff7b72in Te6edf3i
Tff7b72for Te6edf3i Tff7b72in Te6edf3issues
Tb4b4b4)Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ffExpected structural issue in: Tffd700{Te6edf3issuesTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8fftest_entropy_shift_detectionTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Test detection of content type change (e.g. replacing text with random bytes)."""
T8b949e# 1. Start with a highly structured file (low entropy)
T8b949e# Use a non-critical filename to trigger the entropy check branch
Te6edf3data_file Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffuser_data.binTa5d6ff"
Tff7b72with Tffa657openTb4b4b4(Te6edf3data_fileTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Ta5d6ffbTa5d6ff"Ta5d6ffATa5d6ff" Tff7b72* T79c0ff5000Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffsave_manifestTb4b4b4(Tb4b4b4)

T8b949e# 2. Replace with high-entropy data (random bytes)
Tff7b72with Tffa657openTb4b4b4(Te6edf3data_fileTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3osTff7b72.Td2a8ffurandomTb4b4b4(T79c0ff5000Tb4b4b4)Tb4b4b4)

Te6edf3is_okTb4b4b4, Te6edf3issues Tff7b72= Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffcheck_integrityTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertFalseTb4b4b4(Te6edf3is_okTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffIntegrity should fail. Issues: Tffd700{Te6edf3issuesTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertTrueTb4b4b4(
Tffa657anyTb4b4b4(Ta5d6ff"Ta5d6ffNon-linear content shiftTa5d6ff" Tff7b72in Te6edf3i Tff7b72or Ta5d6ff"Ta5d6ffEntropy ΔTa5d6ff" Tff7b72in Te6edf3i Tff7b72for Te6edf3i Tff7b72in Te6edf3issuesTb4b4b4)Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ffExpected entropy shift in: Tffd700{Te6edf3issuesTffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)

Tff7b72def Td2a8fftest_ignore_patterns_extensiveTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Verify all volatile LXMF/RNS patterns are correctly filtered."""
Te6edf3volatile_files Tff7b72= Tb4b4b4[
Ta5d6ff"Ta5d6fflxmf_router/lxmf/outbound_stamp_costsTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_router/storage/some_volatile_fileTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_router/announces/ann_dataTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fflxmf_router/tmp/uploadingTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdatabase.db-walTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdatabase.db-shmTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffsomething.tmpTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff.DS_StoreTa5d6ff"Tb4b4b4,
Tb4b4b4]

Tff7b72for Te6edf3v Tff7b72in Te6edf3volatile_filesTb4b4b4:
Te6edf3rel_path Tff7b72= Te6edf3PathTb4b4b4(Te6edf3vTb4b4b4)
Te6edf3full_path Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Te6edf3rel_path
Te6edf3full_pathTff7b72.Td2a8ffparentTff7b72.Td2a8ffmkdirTb4b4b4(Te6edf3parentsTff7b72=Tff7b72TrueTb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Te6edf3full_pathTff7b72.Td2a8fftouchTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertTrueTb4b4b4(
Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ff_should_ignoreTb4b4b4(Tffa657strTb4b4b4(Te6edf3rel_pathTb4b4b4)Tb4b4b4)Tb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffFailed to ignore Tffd700{Te6edf3vTffd700}Ta5d6ff"
Tb4b4b4)

Tff7b72def Td2a8fftest_critical_file_protectionTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Ensure identity and config changes are always treated as critical."""
Te6edf3id_file Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffidentityTa5d6ff"
Te6edf3id_fileTff7b72.Td2a8ffwrite_textTb4b4b4(Ta5d6ff"Ta5d6ffsecure_keyTa5d6ff"Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffsave_manifestTb4b4b4(Tb4b4b4)

T8b949e# Minor modification (stays low entropy)
Te6edf3id_fileTff7b72.Td2a8ffwrite_textTb4b4b4(Ta5d6ff"Ta5d6ffsecure_kezTa5d6ff"Tb4b4b4)

Te6edf3is_okTb4b4b4, Te6edf3issues Tff7b72= Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffcheck_integrityTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertFalseTb4b4b4(Te6edf3is_okTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertTrueTb4b4b4(Tffa657anyTb4b4b4(Ta5d6ff"Ta5d6ffCritical security componentTa5d6ff" Tff7b72in Te6edf3i Tff7b72for Te6edf3i Tff7b72in Te6edf3issuesTb4b4b4)Tb4b4b4)

Tff7b72def Td2a8fftest_missing_file_detectionTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Verify missing files are detected even if not critical."""
Te6edf3misc_file Tff7b72= Tff7b72selfTff7b72.Td2a8fftest_dir Tff7b72/ Ta5d6ff"Ta5d6ffmisc.txtTa5d6ff"
Te6edf3misc_fileTff7b72.Td2a8ffwrite_textTb4b4b4(Ta5d6ff"Ta5d6ffdataTa5d6ff"Tb4b4b4)

Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffsave_manifestTb4b4b4(Tb4b4b4)
Te6edf3misc_fileTff7b72.Td2a8ffunlinkTb4b4b4(Tb4b4b4)

Te6edf3is_okTb4b4b4, Te6edf3issues Tff7b72= Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffcheck_integrityTb4b4b4(Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertFalseTb4b4b4(Te6edf3is_okTb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertTrueTb4b4b4(Tffa657anyTb4b4b4(Ta5d6ff"Ta5d6ffFile missing: misc.txtTa5d6ff" Tff7b72in Te6edf3i Tff7b72for Te6edf3i Tff7b72in Te6edf3issuesTb4b4b4)Tb4b4b4)

Tff7b72def Td2a8fftest_manifest_versioningTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Verify the manifest includes the new version and metadata fields."""
Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffsave_manifestTb4b4b4(Tb4b4b4)

Tff7b72with Tffa657openTb4b4b4(Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffmanifest_pathTb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3manifest Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadTb4b4b4(Te6edf3fTb4b4b4)

Tff7b72selfTff7b72.Td2a8ffassertEqualTb4b4b4(Te6edf3manifestTb4b4b4[Ta5d6ff"Ta5d6ffversionTa5d6ff"Tb4b4b4]Tb4b4b4, T79c0ff2Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertInTb4b4b4(Ta5d6ff"Ta5d6ffmetadataTa5d6ff"Tb4b4b4, Te6edf3manifestTb4b4b4)

T8b949e# Check if database metadata exists
Te6edf3db_rel Tff7b72= Tffa657strTb4b4b4(Tff7b72selfTff7b72.Td2a8ffdb_pathTff7b72.Td2a8ffrelative_toTb4b4b4(Tff7b72selfTff7b72.Td2a8fftest_dirTb4b4b4)Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertInTb4b4b4(Te6edf3db_relTb4b4b4, Te6edf3manifestTb4b4b4[Ta5d6ff"Ta5d6ffmetadataTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertInTb4b4b4(Ta5d6ff"Ta5d6ffentropyTa5d6ff"Tb4b4b4, Te6edf3manifestTb4b4b4[Ta5d6ff"Ta5d6ffmetadataTa5d6ff"Tb4b4b4]Tb4b4b4[Te6edf3db_relTb4b4b4]Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffassertInTb4b4b4(Ta5d6ff"Ta5d6ffsizeTa5d6ff"Tb4b4b4, Te6edf3manifestTb4b4b4[Ta5d6ff"Ta5d6ffmetadataTa5d6ff"Tb4b4b4]Tb4b4b4[Te6edf3db_relTb4b4b4]Tb4b4b4)

Tff7b72def Td2a8fftest_database_size_divergenceTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Verify size changes are caught when hash changes but entropy is similar."""
Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffsave_manifestTb4b4b4(Tb4b4b4)

T8b949e# Grow the database with similar content
Te6edf3conn Tff7b72= Te6edf3sqlite3Tff7b72.Td2a8ffconnectTb4b4b4(Tff7b72selfTff7b72.Td2a8ffdb_pathTb4b4b4)
Te6edf3connTff7b72.Td2a8ffexecuteTb4b4b4(Ta5d6ff"Ta5d6ffINSERT INTO data (val) VALUES (?)Ta5d6ff"Tb4b4b4, Tb4b4b4(Ta5d6ff"Ta5d6ffmore contentTa5d6ff" Tff7b72* T79c0ff100Tb4b4b4,Tb4b4b4)Tb4b4b4)
Te6edf3connTff7b72.Td2a8ffcommitTb4b4b4(Tb4b4b4)
Te6edf3connTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)

Te6edf3is_okTb4b4b4, Te6edf3issues Tff7b72= Tff7b72selfTff7b72.Td2a8ffmanagerTff7b72.Td2a8ffcheck_integrityTb4b4b4(Tb4b4b4)
T8b949e# Even if entropy shift is low, hash and size changed
Tff7b72if Tff7b72not Te6edf3is_okTb4b4b4:
Tff7b72selfTff7b72.Td2a8ffassertTrueTb4b4b4(Tffa657anyTb4b4b4(Ta5d6ff"Ta5d6ffDatabaseTa5d6ff" Tff7b72in Te6edf3i Tff7b72for Te6edf3i Tff7b72in Te6edf3issuesTb4b4b4)Tb4b4b4)


Tff7b72if Tff7b72__name__ Tff7b72== Ta5d6ff"Ta5d6ff__main__Ta5d6ff"Tb4b4b4:
Te6edf3unittestTff7b72.Td2a8ffmainTb4b4b4(Tb4b4b4)


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────